-
Notifications
You must be signed in to change notification settings - Fork 910
Use scoped rayon pool for backfill chain segment processing #7924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Use scoped rayon pool for backfill chain segment processing #7924
Conversation
0e9d888
to
47a80e5
Compare
…ckfill-verify-kzg-use-scoped-rayon
…ckfill-verify-kzg-use-scoped-rayon
I've done a bunch of testing on backfill with the global rayon pool vs scoped rayon pool usage. The biggest difference is that KZG verification takes more than double the time with a scoped rayon pool (using a max of~25% of cpu threads) vs the global rayon pool. Additionally I did see lower cpu usage on average in the scoped pool case. My node did not have any issues following the chain in either case during backfill. So I can't argue that this change is absolutely necessary. But it is a safe and relatively simple optimization to make. It can potentially help protect nodes from having issues during backfill sync, so maybe thats a good enough reason to include this in our 8.0.0 release candidate I haven't see any evidence that something like #7789 is required. It seems like the OS scheduler is good enough at figuring things out with the current scoped rayon pool usage. If we were to expand our scoped rayon pool usage to other work events, #7789 could potentially become more relevant. In a future iteration (or in this PR) we could make the low priority rayon pool configurable via cli flag to give users additional control over the speed of backfill. This is probably unnecessarily at the moment, but could potentially become useful if we expand scoped rayon thread pool usage. Another TODO could be to introduce a high priority rayon pool and always avoid scheduling rayon tasks on the global thread pool. It remains to be seen if that would be useful considering our current rayon usage. |
Some required checks have failed. Could you please take a look @jimmygchen? 🙏 |
I've added a high priority rayon pool thats used during backfill when the flag |
…ckfill-verify-kzg-use-scoped-rayon
Something about the changes to the backfill work event are causing a test to fail in a non-deterministic manner. I'm wondering if its because the backfill task is now blocking instead of async? |
…ckfill-verify-kzg-use-scoped-rayon
I made a small tweak to |
Some required checks have failed. Could you please take a look @jimmygchen? 🙏 |
Hi @ethDreamer would you mind reviewing this please 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eserilev looks great, I've added some comments. Let me know what you think!
pub low_priority_threadpool: Arc<ThreadPool>, | ||
/// Larger rayon thread pool for high-priority, compute-intensive tasks. | ||
/// By default 100% of CPUs. | ||
pub high_priority_threadpool: Arc<ThreadPool>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're allocating work to this thread pool, we might need to be a bit careful with using the global thread pool, because now we can allocate work to:
- global thread pool with
num_cpus
threads - high_priority_threadpool with
num_cpus
threads - low priority threadpool with 2 min threads
so we now let rayon use 2 * num_cpus
+ 2 threads, which may even cause more oversubscription.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a few into_par_iter
usages, but i think the only compute intensive ones are reconstruction
and verify_cell_proof_batch
so maybe we should spawn those tasks with this thread pool too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think rayon is doing 2 * num_cpus
threads if we schedule one work event on the global pool and one work event on the high priority thread pool.
This is an article from one of the rayon maintainers
https://smallcultfollowing.com/babysteps/blog/2015/12/18/rayon-data-parallelism-in-rust/
It's a bit old, but he explains the work stealing technique that rayon does to find idle threads. the tldr there being that rayon just constantly tries to steal idle threads to execute tasks in parallel.
I also checked with our AI overlords, and they mentioned that work scheduled in the global pool might have a bit more difficulty stealing threads from work scheduled in the scoped pool (and vice versa).
As far as i can tell I think we could actually probably do away with the high priority thread pool (unless we want it to be num_cpus < max_cpus
) as it doesn't really provide any benefit vs the global thread pool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i haven't dug enough, but my understanding was:
- work-stealing only exists between threads in the same threadpool
- "idle thread" means the rayon thread has no task in it's local work queue (not the phyiscal CPU core)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I'm just thinking that the global thread pool is providing the same functionality as the high prio thread pool with a bit less effort code wise. Because if we wanted to run something like reconstruction in the scoped high prio thread pool, I think we'd need to make the reconstruction work event a blocking task (like what you did with ChainSegmentBackFill
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah agree!
use std::sync::Arc; | ||
|
||
pub const DEFAULT_LOW_PRIORITY_DIVISOR: usize = 4; | ||
const MINIMUM_LOW_PRIORITY_THREAD_COUNT: usize = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can even lower this to 1 (suggested by @ethDreamer)
With EIP-7870, the min requirement for a full node is 4 phyiscal cores / 8 threads, so in production envs - we should get 2 at the minimum.
However, if the process is run on a containerised environment with CPU limits (eg. for devnet testing), then we might end up allocating more than we should, e.g. if max_cpu=4
, we'd end up allocating half of the available threads.
i think it probably safer to make this as low as possible (1), and this should be fine because as the name suggests, it's low priority - so running on a single thread should be fine.
@@ -623,11 +625,21 @@ impl TestRig { | |||
&mut self, | |||
expected: &[&str], | |||
timeout: Duration, | |||
ignore_worker_freed: bool, | |||
ignore_nothing_to_do: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a small tweak to assert_event_journal_with_timeout to ignore WORKER_FREED and NOTHING_TO_DO events for the test case that was failing non-deterministically.
Do you think the non-deterministism was due to backfill rate limiting? would disabling it fixes it and speed up the tests?
let beacon_processor_config = BeaconProcessorConfig {
enable_backfill_rate_limiting: false,
..Default::default()
};
Issue Addressed
Part of #7866
rayon
to speed up batch KZG verification #7921In the above PR, we enabled rayon for batch KZG verification in chain segment processing. However, using the global rayon thread pool for backfill is likely to create resource contention with higher-priority beacon processor work.
Proposed Changes
This PR introduces a dedicated low-priority rayon thread pool
LOW_PRIORITY_RAYON_POOL
and uses it for processing backfill chain segments.This prevents backfill KZG verification from using the global rayon thread pool and competing with high-priority beacon processor tasks for CPU resources.
However, this PR by itself doesn't prevent CPU oversubscription because other tasks could still fill up the global rayon thread pool, and having an extra thread pool could make things worse. To address this we need the beacon
processor to coordinate total CPU allocation across all tasks, which is covered in: